package cn.geminis.manage.service.controller;

import cn.geminis.core.util.DateUtils;
import cn.geminis.core.util.PasswordUtils;
import cn.geminis.manage.core.entity.Role;
import cn.geminis.manage.service.model.AdministratorRepository;
import cn.geminis.web.logging.LoggingMdcName;
import cn.geminis.web.logging.LoggingMethod;
import cn.geminis.web.logging.LoggingParam;
import cn.geminis.web.security.*;
import org.apache.commons.lang.StringUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Allen
 */
@RestController
@RequestMapping("/login")
public class LoginController {

    @Autowired
    private VerifyCode verifyCode;

    @Autowired
    private AdministratorRepository repository;

    @Value("${geminis.manage.login.checkCode:true}")
    private boolean checkCode;

    @GetMapping(path = "/code")
    public String code() {
        return TokenUtils.createCodeToken(verifyCode.createCode());
    }

    @GetMapping("/codeImage/{codeToken}")
    public void codeImage(@PathVariable String codeToken, HttpServletResponse response) {
        var code = TokenUtils.verifyCodeToken(codeToken);

        response.setDateHeader("Expires", 0);
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        response.addHeader("Cache-Control", "post-check=0, pre-check=0");
        response.setHeader("Pragma", "no-cache");
        response.setContentType("image/jpeg");

        var codeImage = verifyCode.build(code);
        try (var out = response.getOutputStream()) {
            ImageIO.write(codeImage, "jpg", out);
            out.flush();
        } catch (IOException e) {
            throw new RuntimeException("生成验证码图片错误", e);
        }
    }

    //采用GET方式，便于单点登录集成
    @RequestMapping(value = "/pwd", method = {RequestMethod.GET, RequestMethod.POST})
    @LoggingMethod("管理员登录")
    public UserDetails loginPwd(@LoggingParam("登录名") String username,
                             String password,
                             String code,
                             String codeToken,
                             HttpServletRequest request,
                             HttpServletResponse response) {

        var loginUser = new JwtUser(username, username, null, null);
        var authentication = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        authentication.setAuthenticated(false);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        if (this.checkCode) {
            var codeServer = TokenUtils.verifyCodeToken(codeToken);
            if (!codeServer.equalsIgnoreCase(code)) {
                throw new RuntimeException("验证码不匹配");
            }
        }

        var admin = this.repository
                .include("roles.functions")
                .include("adminPassword")
                .where("id", username)
                .findOne()
                .orElseThrow(() -> new RuntimeException("登录名或密码错误"));

        var saltPassword = PasswordUtils.addSalt(password, admin.getAdminPassword().getSalt());

        if (!StringUtils.equalsIgnoreCase(saltPassword, admin.getAdminPassword().getPassword())) {
            throw new RuntimeException("登录名或密码错误");
        }

        loginUser = new JwtUser(
                admin.getId(),
                admin.getName(),
                admin.getRoles().stream().flatMap(role -> role.getFunctions().stream())
                        .map(function -> new SimpleGrantedAuthority(function.getId()))
                        .collect(Collectors.toList()),
                HttpServletRequestUtils.getIpAddress(request));

        authentication = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authentication);

        var token = TokenUtils.createUserToken(loginUser);
        response.addHeader(HttpHeaders.AUTHORIZATION + "-Manage", token);
        response.addHeader(HttpHeaders.AUTHORIZATION + "-Manage-Expire",
                DateUtils.toString(LocalDateTime.now().plusMinutes(TokenUtils.getExpireMinutes()),
                        "yyyy-MM-dd HH:mm:ss"));

        MDC.put(LoggingMdcName.USER_ID, String.format("%s(%s)", loginUser.getName(), loginUser.getUsername()));
        MDC.put(LoggingMdcName.CLIENT_IP, HttpServletRequestUtils.getIpAddress(request));

        return loginUser;
    }

    @PutMapping("/changePwd")
    @PreAuthorize("isAuthenticated()")
    @LoggingMethod("修改登录密码")
    public void changePassword(String oldPassword, String newPassword) {
        var user = LoginUserUtils.getLoginUser();

        var administrator = repository
                .include("adminPassword")
                .where("id", user.getUsername())
                .findOne()
                .orElseThrow(() -> new RuntimeException("当前登录用户不存在"));
        var adminPassword = administrator.getAdminPassword();

        var saltPassword = PasswordUtils.addSalt(oldPassword, adminPassword.getSalt());
        if (!saltPassword.equals(adminPassword.getPassword())) {
            throw new RuntimeException("当前密码不匹配");
        }

        saltPassword = PasswordUtils.addSalt(newPassword, adminPassword.getSalt());
        if (saltPassword.equals(adminPassword.getPassword())
                || saltPassword.equals(adminPassword.getPassword2())
                || saltPassword.equals(adminPassword.getPassword3())) {
            throw new RuntimeException("密码与前三次密码重复");
        }

        adminPassword.setPassword3(adminPassword.getPassword2());
        adminPassword.setPassword2(adminPassword.getPassword());
        adminPassword.setPassword(saltPassword);
        adminPassword.setPasswordChangeTime(LocalDateTime.now());
        repository.save(administrator);
    }

    @GetMapping("/manageRole")
    @PreAuthorize("hasAuthority('manage')")
    public List<Role> manageRole() {

        var user = LoginUserUtils.getLoginUser();

        return this.repository.getOne(user.getUsername())
                .getRoles()
                .stream()
                .flatMap(role -> role.getRolesManage().stream())
                .distinct()
                .sorted(Comparator.comparing(Role::getName))
                .collect(Collectors.toList());
    }
}
